//please use tsc main.ts -t es6
//i hate javascript.
import Plugin from "../../lib/plugins/plugin.js"
import ChildProcess from "node:child_process"
import FileSystem from "node:fs"
import { setTimeout as SetTimeoutPromise } from "node:timers/promises"
import JsonFile from "jsonfile"

type bool = boolean;
type Dictionary<TKey, TValue> = Map<TKey, TValue>;
//type Nullable<T> = T | null;
//也许没用，但是不写就难受
type oicq_Friend = any;
type oicq_Group = any;
type SendTarget = oicq_Friend | oicq_Group;

class OutputInfo {
  Target: SendTarget;
  HasOutput: bool;
  Buffer: string;
}
class ProcessInfo {
  StdoutInfo: OutputInfo;
  StderrInfo: OutputInfo;
  Process: ChildProcess.ChildProcess;
}
enum TaskType {
  PerMinute,
  PerHour,
  PerDay
}
class Task {
  Type: TaskType;
  Name: string;
  ShellCommand: string;
  //目标主人Uid
  SendTarget: any;
}

let Processes: Dictionary<any, ProcessInfo> = new Map<any, ProcessInfo>();
let Tasks: Task[] = [];
let TaskAbortController: Dictionary<string, AbortController> = new Map<string, AbortController>;

FileSystem.stat("tasks.json", (error, status) => {
  if (error) {
    global.logger.mark("正在创建tasks.json");
    FileSystem.writeFile("tasks.json", JSON.stringify(Tasks), error => {
      if (error)
        global.logger.error(`创建tasks.json时出现错误：${error}`)
    });
  }
  else if (!status.isFile())
    global.logger.error("tasks.json不是文件，请删除它");
  else
    JsonFile
      .readFile("tasks.json")
      .then((data: Task[]) => { Tasks = data })
      .catch((error: any) => { global.logger.error(error) })
      .then(() => {
        Tasks.forEach(task => {
          let abortController = new AbortController();
          let signal = abortController.signal;
          TaskAbortController.set(task.Name, abortController);
          let waitTime: number;
          switch (task.Type) {
            case TaskType.PerMinute:
              waitTime = 60 * 1000;
              break;
            case TaskType.PerHour:
              waitTime = 60 * 60 * 1000;
              break;
            case TaskType.PerDay:
              waitTime = 60 * 60 * 24 * 1000;
              break;
          }
          let call = () => SetTimeoutPromise(waitTime, undefined, { signal })
            .then(() => {
              global.logger.mark(`定时任务${task.Name}开始`);
              let process: ChildProcess.ChildProcess;
              process = ChildProcess.spawn(task.ShellCommand, { shell: true });
              signal.onabort = () => {
                if (!process.kill(15))
                  process.kill(9);
              };
              let stdoutBuffer = "", stderrBuffer = "", hasStdout = false, hasStderr = false;
              process.stdout?.on("data", (out) => {
                if (hasStdout)
                  stdoutBuffer += out;
                else {
                  hasStdout = true;
                  stdoutBuffer = out;
                  setTimeout(() => {
                    global.logger.mark(`Send to ${task.SendTarget} : ${stdoutBuffer}`);
                    global.Bot.pickFriend(task.SendTarget).sendMsg(`任务${task.Name}:stdout:\n${stdoutBuffer}`);
                    hasStdout = false, stdoutBuffer = "";
                  }, 1000);
                }
              });
              process.stderr?.on("data", (out) => {
                if (hasStderr)
                  stderrBuffer += out;
                else {
                  hasStderr = true;
                  stderrBuffer = out;
                  setTimeout(() => {
                    global.logger.mark(`Send to ${task.SendTarget} : ${stderrBuffer}`);
                    global.Bot.pickFriend(task.SendTarget).sendMsg(`任务${task.Name}:stderr:\n${stderrBuffer}`);
                    hasStderr = false, stderrBuffer = "";
                  }, 1000);
                }
              });
              global.logger.mark(`定时任务${task.Name}结束`);
              if(!signal.aborted)
                call();
            });
          call();
          global.logger.mark(`定时任务${task.Name}已加载`);
        });
      });
});

export class BetterRc extends Plugin {
  //只是给tsc看的
  e: any;
  user_id: any;
  constructor() {
    super({
      name: '远程命令',
      dsc: '在运行bot的计算机上执行任何命令（仅限管理员，高危）',
      event: 'message',
      priority: 5000,
      rule: [
        {
          reg: '^#执行',
          fnc: 'StartShell',
          permission: "master"
        },
        {
          reg: "^#停止执行$",
          fnc: "StopShell",
          permission: "master"
        },
        {
          reg: "^#输入",
          fnc: "AddInput",
          permission: "master"
        },
        {
          reg: "^#重定向输出$",
          fnc: "ChangeTarget",
          permission: "master"
        },
        {
          reg: "^#添加任务",
          fnc: "AddTask",
          permission: "master"
        },
        {
          reg: "^#删除任务",
          fnc: "RemoveTask",
          permission: "master"
        }
      ],
      handler: undefined,
      namespace: undefined
    })
  }
  async StartShell() {
    if (Processes.has(this.e.user_id)) {
      this.reply("当前有未终止的进程，请发送 #停止执行 后再试。");
      return;
    }
    let process: ChildProcess.ChildProcess;
    let msg: string = this.e.msg.substring("#执行".length).trim();
    let procoutinf = new OutputInfo();
    let target = this.e.user_id;
    procoutinf.Target = this.e.group === undefined ? this.e.friend : this.e.group, procoutinf.HasOutput = false, procoutinf.Buffer = "";
    process = ChildProcess.spawn(msg, { shell: true });
    let processInfo: ProcessInfo = new ProcessInfo();
    processInfo.Process = process, processInfo.StderrInfo = Object.assign({}, procoutinf), processInfo.StdoutInfo = procoutinf;
    process.stdout?.on("data", (out) => {
      global.logger.mark(`Process crated by ${target}: stdout : ${out}`)
      if (processInfo.StdoutInfo.HasOutput)
        processInfo.StdoutInfo.Buffer += out;
      else {
        processInfo.StdoutInfo.HasOutput = true;
        processInfo.StdoutInfo.Buffer = out;
        setTimeout(() => {
          global.logger.mark(`Send to ${processInfo.StdoutInfo.Target} : ${processInfo.StdoutInfo.Buffer}`);
          processInfo.StdoutInfo.Target.sendMsg(msg + ":stdout:\n" + processInfo.StdoutInfo.Buffer);
          processInfo.StdoutInfo.HasOutput = false, processInfo.StdoutInfo.Buffer = "";
        }, 1000);
      }
    });
    process.stderr?.on("data", (out) => {
      global.logger.mark(`Process crated by ${target} : stderr : ${out}`)
      if (processInfo.StderrInfo.HasOutput)
        processInfo.StderrInfo.Buffer += out;
      else {
        processInfo.StderrInfo.HasOutput = true;
        processInfo.StderrInfo.Buffer = out;
        setTimeout(() => {
          global.logger.mark(`Send to ${processInfo.StderrInfo.Target} : ${processInfo.StderrInfo.Buffer}`);
          processInfo.StderrInfo.Target.sendMsg(msg + ":stderr:\n" + processInfo.StderrInfo.Buffer);
          processInfo.StderrInfo.HasOutput = false, processInfo.StderrInfo.Buffer = "";
        }, 1000);
      }
    });
    Processes.set(this.e.user_id, processInfo);
  }
  async StopShell() {
    if (Processes.has(this.e.user_id)) {
      let process = Processes.get(this.e.user_id);
      Processes.delete(this.e.user_id);
      if (!process?.Process.kill(15)) {
        this.reply("kill -15失败，正在尝试使用kill -9")
        process?.Process.kill(9);
      }
      this.reply("进程已终止，返回代码为" + process?.Process.exitCode);
    }
    else {
      this.reply("当前没有进程。");
    }
  }
  async AddInput() {
    if (Processes.has(this.e.user_id)) {
      let process = Processes.get(this.e.user_id);
      let msg = this.e.msg.substring("#输入 ".length).toString() + "\n";
      process?.Process.stdin?.write(msg);
    }
    else {
      this.reply("当前没有进程。");
    }
  }
  async ChangeTarget() {
    if (Processes.has(this.e.user_id)) {
      let procecss = Processes.get(this.e.user_id);
      if (procecss !== undefined) {
        procecss.StderrInfo.Target = this.e.group === undefined ? this.e.friend : this.e.group;
        procecss.StdoutInfo.Target = this.e.group === undefined ? this.e.friend : this.e.group;
        return;
      }
    }
    this.reply("当前没有进程。");
  }
  SaveFile() {
    global.logger.mark("正在保存文件......");
    JsonFile.writeFile("tasks.json", Tasks, (error) => {
      if (error) {
        global.logger.error(error);
        this.reply(`保存文件时出现错误：${error}`);
      }
    });
  }
  async AddTask() {
    let input: string[] = this.e.msg.match("(?:^#添加任务 )([^ ]+) ([^ ]+) ([^\r]+)");
    let task: Task = new Task();
    switch (input[1]) {
      case "每分钟":
      case "每分":
        task.Type = TaskType.PerMinute;
        break;
      case "每小时":
      case "每时":
        task.Type = TaskType.PerHour;
        break;
      case "每天":
        task.Type = TaskType.PerDay;
        break;
      default:
        this.reply("任务类型错误！");
        return;
    }
    if (TaskAbortController.has(input[2])) {
      this.reply("任务重复！");
      return;
    }
    task.Name = input[2], task.ShellCommand = input[3], task.SendTarget = this.e.user_id;
    Tasks.push(task);
    this.SaveFile();
    let abortController = new AbortController();
    let signal = abortController.signal;
    TaskAbortController.set(task.Name, abortController);
    let waitTime: number;
    switch (task.Type) {
      case TaskType.PerMinute:
        waitTime = 60 * 1000;
        break;
      case TaskType.PerHour:
        waitTime = 60 * 60 * 1000;
        break;
      case TaskType.PerDay:
        waitTime = 60 * 60 * 24 * 1000;
        break;
    }
    let call = () => SetTimeoutPromise(waitTime, undefined, { signal })
      .then(() => {
        global.logger.mark(`定时任务${task.Name}开始`);
        let process: ChildProcess.ChildProcess;
        process = ChildProcess.spawn(task.ShellCommand, { shell: true });
        signal.onabort = () => {
          if (!process.kill(15))
            process.kill(9);
        };
        let stdoutBuffer = "", stderrBuffer = "", hasStdout = false, hasStderr = false;
        process.stdout?.on("data", (out) => {
          if (hasStdout)
            stdoutBuffer += out;
          else {
            hasStdout = true;
            stdoutBuffer = out;
            setTimeout(() => {
              global.logger.mark(`Send to ${task.SendTarget} : ${stdoutBuffer}`);
              global.Bot.pickFriend(task.SendTarget).sendMsg(`任务${task.Name}:stdout:\n${stdoutBuffer}`);
              hasStdout = false, stdoutBuffer = "";
            }, 1000);
          }
        });
        process.stderr?.on("data", (out) => {
          if (hasStderr)
            stderrBuffer += out;
          else {
            hasStderr = true;
            stderrBuffer = out;
            setTimeout(() => {
              global.logger.mark(`Send to ${task.SendTarget} : ${stderrBuffer}`);
              global.Bot.pickFriend(task.SendTarget).sendMsg(`任务${task.Name}:stderr:\n${stderrBuffer}`);
              hasStderr = false, stderrBuffer = "";
            }, 1000);
          }
        });
        global.logger.mark(`定时任务${task.Name}结束`);
        if(!signal.aborted)
          call();
      });
    call();
    global.logger.mark(`定时任务${task.Name}已加载`);
  }
  async RemoveTask() {
    let taskName: string = this.e.msg.match("(?:^#删除任务 )([^ ]+)")[1];
    if(!TaskAbortController.has(taskName))
    {
      this.reply("任务不存在！");
      return;
    }
    let abortController = TaskAbortController.get(taskName);
    TaskAbortController.delete(taskName);
    abortController?.abort();
    Tasks = Tasks.filter(task => task.Name != taskName);
    this.SaveFile();
    global.logger.info(`任务${taskName}已经删除`);
  }
}